# -*-coding:utf-8-*-

# 广义正交犹豫模糊集 类 —— 处理模糊数组成的向量
class Operator_QROHFN():
    """
        QROHFPR 广义正交犹豫模糊集 类
    """

    def __init__(self, data_list=[], weight_list=[], q=2, y=2):
        """
            funciton    : 初始化类的初始值, 具有默认初始值, 和检查数据列表和权重列表维度匹配的功能
            data_list   : 数据集, 单个数据形式:（[ui]，[vi]）,带有默认初始化
            weight_list : 权重集, list type, 数量 n ,带有默认初始化
            q           : 参数 q —— 用于 广义正交
            y           : 参数 y —— 用于其他算子的备用
            return      : 无返回值
        """
        if len(data_list):
            self.data_list = data_list  # 初始化数据列表
        else:  # 默认初始为一个 4 个模糊数的列表
            self.data_list = [
                ([0.5000, 0.5000, 0.5000], [0.5000, 0.5000, 0.5000]),
                ([0.2100, 0.2160, 0.2280], [0.2100, 0.2333, 0.2410]),
                ([0.3333, 0.3770, 0.3940], [0.3800, 0.3870, 0.3950]),
                ([0.3600, 0.3700, 0.3900], [0.3820, 0.3870, 0.4500])
            ]

        if len(weight_list):
            self.weight_list = weight_list  # 初始化权重列表
        else:  # 默认初始为一个 4 个权重值的向量 —— 等权重
            self.weight_list = [0.25, 0.25, 0.25, 0.25]

        # 如果存在数据和权重列表不匹配的情况, 则做切片处理
        if len(self.data_list) != len(self.weight_list):
            min_len = min(len(self.data_list), len(self.weight_list))
            self.data_list = self.data_list[::min_len]
            self.weight_list = self.weight_list[:min_len]

        self.q = q  # 初始化参数 q
        self.y = y  # 初始化参数 y

        self.ifshowProcess = 1
        pass

    # 基础运算法则相关     改写 OK

    def add(self, a1, a2, q=0, *waste1, **waste2):
        """
            funciton    : 广义正交犹豫模糊集的 和 运算
            a1          : 一个模糊集, 形式:（[xi]，[yi]）
            a2          : 一个模糊集, 形式:（[xi]，[yi]）
            q           : 广义正交所需q值 要求大于等于 1
            return      : 加法结果, 形式:（[xi]，[yi]）
        """
        # l —— list 的意思，不是 left
        # 提取隶属度和非隶属度的元素列表
        u_l_1, u_l_2, v_l_1, v_l_2 = a1[0], a2[0], a1[1], a2[1]
        if q < 1:
            q = self.q
        u_l_re = []
        v_l_re = []
        # 默认隶属度和非隶属的长度是一样的，没做检查处理
        for i in range(len(u_l_1)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            u_1, u_2 = (u_l_1[i]) ** q, (u_l_2[i]) ** q
            u_re = (u_1 + u_2 - u_1 * u_2) ** (1 / q)
            u_l_re.append(u_re)
        for i in range(len(v_l_1)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            v_1, v_2 = (v_l_1[i]) ** q, (v_l_2[i]) ** q
            v_re = (v_1 * v_2) ** (1 / q)
            v_l_re.append(v_re)
        return (u_l_re, v_l_re)

    def multi(self, a1, a2, q=0, *waste1, **waste2):
        """
            funciton    : 区间值广义正交模糊集的 积 运算
            a1          : 一个模糊集, 形式:（[xi]，[yi]）
            a2          : 一个模糊集, 形式:（[xi]，[yi]）
            q           : 广义正交所需q值 要求大于等于 1
            return      : 乘法结果, 形式:（[xi]，[yi]）
        """
        # l —— list 的意思，不是 left
        # 提取隶属度和非隶属度的元素列表
        u_l_1, u_l_2, v_l_1, v_l_2 = a1[0], a2[0], a1[1], a2[1]
        if q < 1:
            q = self.q

        u_l_re = []
        v_l_re = []
        # 默认隶属度和非隶属的长度是一样的，没做检查处理
        for i in range(len(u_l_1)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            u_1, u_2 = (u_l_1[i]) ** q, (u_l_2[i]) ** q
            u_re = (u_1 * u_2) ** (1 / q)
            u_l_re.append(u_re)
        for i in range(len(v_l_1)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            v_1, v_2 = (v_l_1[i]) ** q, (v_l_2[i]) ** q
            v_re = (v_1 + v_2 - v_1 * v_2) ** (1 / q)
            v_l_re.append(v_re)
        return (u_l_re, v_l_re)

    def kmulti(self, a, k, q=0, *waste1, **waste2):
        """
            function  : 区间值广义正交模糊集的 数乘 运算
            a         : 一个模糊集, 形式:（[xi]，[yi]）
            q         : 广义正交所需q值 要求大于等于 1
            k         : ka 的常数 k
            return    : 数乘 运算法则 的值，形式:（[xi]，[yi]）
        """
        # l —— list 的意思，不是 left
        # 提取隶属度和非隶属度的元素列表
        u_l_a, v_l_a = a[0], a[1]
        if q <= 0:
            q = self.q

        u_l_re = []
        v_l_re = []
        # 默认隶属度和非隶属的长度是一样的，没做检查处理
        for i in range(len(u_l_a)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            u = (u_l_a[i]) ** q
            u_re = (1 - (1 - u) ** k) ** (1 / q)
            u_l_re.append(u_re)
        for i in range(len(v_l_a)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            v = (v_l_a[i]) ** q
            v_re = (v ** k) ** (1 / q)
            v_l_re.append(v_re)
        return (u_l_re, v_l_re)

    def pow(self, a, k, q=0, *waste1, **waste2):
        """
            function : 区间值广义正交模糊集的 乘方 运算
            a         : 一个模糊集, 形式:（[xi]，[yi]）
            q         : 广义正交所需q值 要求大于等于 1
            k         : a^k 的常数 k
            return   : 乘方 运算法则 的值，形式：（[xi]，[yi]）
        """
        # l —— list 的意思，不是 left
        # 提取隶属度和非隶属度的元素列表
        u_l_a, v_l_a = a[0], a[1]
        if q <= 0:
            q = self.q

        u_l_re = []
        v_l_re = []
        # 默认隶属度和非隶属的长度是一样的，没做检查处理
        for i in range(len(u_l_a)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            u = (u_l_a[i]) ** q
            u_re = (u ** k) ** (1 / q)
            u_l_re.append(u_re)
        for i in range(len(v_l_a)):
            # 提取隶属度和隶属度列表中的元素，并做 幂 处理
            v = (v_l_a[i]) ** q
            v_re = (1 - (1 - v) ** k) ** (1 / q)
            v_l_re.append(v_re)
        return (u_l_re, v_l_re)

    def pi(self, a, q=0, *waste1, **waste2):
        """
            function : 求犹豫度
            a        : 一个模糊集, 形式:（[xi]，[yi]）
            *waste1  : 回收多余的输入参数
            **waste2 : 回收多余的输入键值对参数
            return   : 犹豫度列表，形式： [pi(i)]
        """
        # 提取模糊数的隶属度和非隶属度
        u_list, v_list = a[0], a[1]
        if q < 1:
            q = self.q
        # 检查uv区间内元素个数，并做相应处理
        if len(u_list) != len(v_list):
            print("该模糊数不符合 QROHFPR 广义正交犹豫模糊数的定义")
            return []
        # 犹豫度列表初始化
        pi_list = []
        for i in range(len(u_list)):
            pi = pow((1 - pow(v_list[i], q) - pow(v_list[i], q)), (1 / q))
            pi_list.append(pi)
        return pi_list

    def islegal(self, data=[], q=0, *waste1, **waste2):
        """
            function : 检查 data 向量中数据的合法性，是否符合广义正交的定义
            data     : 一个模糊集
            return   : 布尔形式
        """
        # 处理
        if not len(data):
            data = self.data_list
        if q < 1:
            q = self.q
        # 做合法的标记 flag
        flag = 1
        for a in data:
            u_list, v_list = a[0], a[1]
            u_max = max(u_list)
            v_max = max(v_list)
            if (u_max ** q + v_max ** q) > 1:
                flag = 0
                break
        if flag:
            print("在q={:}的条件下，该条数据合法".format(q))
            return True
        else:
            print("在q={:}的条件下，存在非法数据".format(q))
            return False

    def score(self, a, *waste1, **waste2):
        """
            function : 计算得分函数
            a        : 一个模糊集, 形式:（[xi]，[yi]）
            return   : 得分结果
        """
        u_l_a, v_l_a = a[0], a[1]
        score = (1 / len(u_l_a)) * (sum(u_l_a)) - (1 / len(v_l_a)) * (sum(v_l_a))
        return score

    def accuracy(self, a, *waste1, **waste2):
        """
            function : 计算精确值函数
            a        : 一个模糊集, 形式:（[x,y]，[x,y]）
            return   : PWA 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        u_l_a, v_l_a = a[0], a[1]
        score = (1 / len(u_l_a)) * (sum(u_l_a)) + (1 / len(v_l_a)) * (sum(v_l_a))
        return score

    def distance_Hamy(self, a1, a2, *waste1, **waste2):
        """
            function : 计算精确值函数
            a        : 一个模糊集, 形式:（[x,y]，[x,y]）
            return   : PWA 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        from math import fabs
        # l —— list 的意思，不是 left
        # 提取隶属度和非隶属度的元素列表
        u_l_1, u_l_2, v_l_1, v_l_2 = a1[0], a2[0], a1[1], a2[1]
        p_l_1, p_l_2 = self.pi(a1), self.pi(a2)
        length = len(u_l_1)  # 取出 l

        # 定义 距离的初始值
        distance = 0
        for i in range(length):
            u_1, u_2 = u_l_1[i], v_l_2[i]
            v_1, v_2 = v_l_1[i], v_l_2[i]
            p_1, p_2 = p_l_1[i], p_l_2[i]
            dis_i = fabs(u_1 - u_2) + fabs(v_1 - v_2) + fabs(p_1 - p_2)
            distance += dis_i
        distance = distance * (1 / 2 * length)
        return distance

    # 功能性函数相关 —— 改写 OK

    def sortdata(self, data=[]):
        """
            function : 根据模糊数得分排列
            a        : 一个模糊集, 形式:（[x,y]，[x,y]）
            return   : PWA 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        s_list = [[self.score(ai), ai] for ai in self.data_list]
        s_list.sort(key=lambda x: x[0], reverse=True)
        return [ai[1] for ai in s_list]

    def getResult(self, *waste1, **waste2):
        """
            function : 集结结果函数，需要在继承并重写
            return   : 集结后的数据结果，形式：（[ui]，[vi]）
        """
        return ([1, 1, 1], [0, 0, 0])

    def show_3D(self, q_low=3, q_high=100, y_low=3, y_high=100,
                xlabel='q paramater', ylabel='y paramater', zlabel='score',
                title='Sensitivity Analysis of Proposed Operator',
                saveimg=False, savename='operator_3D.jpeg', *waste1, **waste2):
        """
            function : 具有两个参数算子的，参数敏感度分析 —— 三维曲面图, 网上参考code
            q_low    : q 的 min 值
            q_high   : q 的 max 值
            y_low    : y 的 min 值
            y_high   : y 的 max 值
            xyzlabel : 三维图三个轴的名称，string类型
            title    : 标题
            saveimg  : 是否保存图片，默认不保存
            savename : 图片名称
            return   : 无
        """
        import matplotlib.pyplot as plt
        from matplotlib import cm
        from matplotlib.ticker import LinearLocator, FormatStrFormatter
        import numpy as np

        # 将初始画的数据进行北方
        q_copy = self.q
        y_copy = self.y

        # 初始化画布
        fig = plt.figure(figsize=(10, 8))
        ax = fig.gca(projection='3d')
        plt.title(title)
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.set_zlabel(zlabel)

        # 数据的处理
        X = [i for i in range(q_low, q_high)]
        Y = [i for i in range(y_low, y_high)]
        Z = []  # Z 是一个 Table 形状的数据结构
        # 构建时候，x 和 y 列表要颠倒一下
        for y in Y:
            li = []  # 每一行的分数
            self.set_y(y)  # 对 q 有限制可以通过重写 set function 进行
            for q in X:
                # 需要更改的
                self.set_q(q)  # 对 y 有限制可以通过重写 set function 进行
                value = self.getResult()
                li.append(self.score(value))
            Z.append(li)

        # 将数据转化为矩阵，并用 x,y 制作网格
        X = np.array(X)
        Y = np.array(Y)
        Z = np.array(Z)
        X, Y = np.meshgrid(X, Y)

        # 绘制曲面图
        surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, linewidth=0, antialiased=False)
        # 对 Z 轴的处理和定制
        ax.set_zlim(-1.01, 1.01)
        ax.zaxis.set_major_locator(LinearLocator(10))
        ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
        # Add a color bar which maps values to colors.
        fig.colorbar(surf, shrink=0.5, aspect=10)

        # 还原数据
        self.set_q(q_copy)
        self.set_y(y_copy)

        # 绘图显示
        if saveimg:
            plt.savefig(savename)
        plt.show()

    def show_2D_q(self, low=3, high=100, saveimg=False,
                  savename="operator_2D_q.jpeg", *waste1, **waste2):
        """
            function : 测试 q 的敏感度
            low      : q 的 min 值
            low      : q 的 max 值
            saveimg  : 是否保存图片，默认不保存
            savename : 图片名称
            return   : 无
        """
        import matplotlib.pyplot as plt
        import numpy as np
        # 备份 q 参数
        q_copy = self.q

        # 处理数据
        X = [i for i in range(low, high)]
        Y = []
        # 带入不同的 q 值来做对比
        for q in range(low, high):
            # 重置 q
            self.q = q
            try:
                temp_q = self.getResult()
                Y.append(self.score(temp_q))
            except:
                Y.append(0)

        # 回复 q 参数
        self.q = q_copy

        # 设置、保存、显示画布
        plt.figure(figsize=(10, 8))
        plt.plot(np.array(X), np.array(Y))
        if saveimg:
            plt.savefig(savename)
        plt.show()

    def show_2D_y(self, low=3, high=100,
                  saveimg=False, savename='operator_2D_y.jpeg', *waste1, **waste2):
        """
            function : 测试 y 的敏感度
            low      : y 的 min 值
            low      : y 的 max 值
            saveimg  : 是否保存图片，默认不保存
            savename : 图片名称
            return   : 无
        """
        import matplotlib.pyplot as plt
        import numpy as np

        # 备份 y 参数
        y_copy = self.y
        # 处理数据
        X = [i for i in range(low, high)]
        Y = []
        # 带入不同的 y 值来做对比
        for y in range(low, high):
            # 重置 y
            self.y = y
            try:
                temp_y = self.getResult()
                Y.append(self.score(temp_y))
            except:
                Y.append(0)

        # 回复 q 参数
        self.y = y_copy

        # 设置、保存、显示画布
        plt.figure(figsize=(10, 8))
        plt.plot(np.array(X), np.array(Y))
        if saveimg:
            plt.savefig(savename)
        plt.show()


class WA(Operator_QROHFN):
    def getResult(self, *waste1, **waste2):
        """
            function :  计算出 WA 算子的结果
            *waste1  :  回收多余的输入参数
            **waste2 :  回收多余的输入键值对参数
            return   :  WA 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        a_result = self.kmulti(self.data_list[0], self.weight_list[0])
        for i_a in range(1, len(self.data_list)):
            a_1 = a_result
            a_2 = self.kmulti(self.data_list[i_a], self.weight_list[i_a])
            a_result = self.add(a_1, a_2)
        return a_result


class WG(Operator_QROHFN):
    def getResult(self, *waste1, **waste2):
        """
            function :  计算出 WA 算子的结果
            *waste1  :  回收多余的输入参数
            **waste2 :  回收多余的输入键值对参数
            return   :  WA 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        a_result = self.pow(self.data_list[0], self.weight_list[0])
        for i_a in range(1, len(self.data_list)):
            a_1 = a_result
            a_2 = self.pow(self.data_list[i_a], self.weight_list[i_a])
            a_result = self.multi(a_1, a_2)
        return a_result


class WBM(Operator_QROHFN):
    def __init__(self, data_list=[], weight_list=[], q=2, y=2, a=0.5, b=0.5, *waste1, **waste2):
        """
            funciton    : BM 算子的初始化类的初始值，增加两个参数
            data_list   : 数据集, 单个数据形式:（[x,y]，[x,y]）,带有默认初始化
            weight_list : 权重集, list type, 数量 n ,带有默认初始化
            q           : 参数 q
            y           : 参数 y
            a           : 参数 a, Bonferroni 新的
            b           : 参数 b, Bonferroni 新的
            return      : 无返回值
        """
        self.a = a
        self.b = b
        super(WBM, self).__init__(data_list, weight_list, q, y)

        pass

    def getResult(self, *waste1, **waste2):
        """
            function : 集结结果函数，需要在继承并重写
            return   : 集结后的数据结果，形式：（[x,y]，[x,y]）
        """
        # 参数的设置 —— 减少代码复杂、提高易理解程度
        m = len(self.data_list)
        data = self.data_list
        weight = self.weight_list
        a = self.a
        b = self.b

        # 取一个初始值 ，以 a0 * aj 的结果作为初始值 ，参与累加部分
        a_result = self.multi(self.pow(self.kmulti(data[0], m * weight[0]), a),
                              self.pow(self.kmulti(data[1], m * weight[1]), b))

        a_i = self.pow(self.kmulti(data[0], m * weight[0]), a)
        for j in range(2, m):
            a_j = self.pow(self.kmulti(data[j], m * weight[j]), b)
            a_temp = self.multi(a_i, a_j)
            a_result = self.add(a_result, a_temp)

        # ai*aj 的累加部分
        for i in range(1, m):
            a_i = self.pow(self.kmulti(data[i], m * weight[i]), a)
            for j in range(0, m):
                if i != j:
                    a_j = self.pow(self.kmulti(data[j], m * weight[j]), b)
                    a_temp = self.multi(a_i, a_j)
                    a_result = self.add(a_result, a_temp)

        # 数乘部分 k = 1/ m(m-1)
        a_result = self.kmulti(a_result, (1 / (m * (m - 1))))

        # 次方部分 pow = 1/(a+b)
        a_result = self.pow(a_result, (1 / (a + b)))

        return a_result


class OurAO(Operator_QROHFN):
    def getResult(self, *waste1, **waste2):
        data = self.data_list
        weight = self.weight_list
        q = self.q

        u_result = []
        v_result = []

        # 遍历列表 list  —— type [a1:[u..],[v..], a2:[u..],[v..]]
        # 提取每个模糊数对应位置的 u v
        for s in range(len(data[0][0])):
            # 遍历 u v 中每个元素, 做加权累加
            u_sum = 0
            v_sum = 0
            for i in range(len(data)):
                u_sum += (data[i][0][s] ** q) * weight[i]
                v_sum += (data[i][1][s] ** q) * weight[i]
            u_sum = u_sum ** (1 / q)
            v_sum = v_sum ** (1 / q)

            u_result.append(u_sum)
            v_result.append(v_sum)

        return (u_result, v_result)

if __name__ == '__main__':
    para = {
        "data_list": [([0.2100, 0.2160, 0.2280], [0.2100, 0.2333, 0.2410]),
                      ([0.2334, 0.3001, 0.3668], [0.4167, 0.4500, 0.4833]),
                      ([0.2669, 0.3243, 0.3763], [0.2391, 0.3095, 0.4603])],
        "weight_list": [0.3, 0.2, 0.5],
        "q": 1
    }
    ao = OurAO(**para)
    print(ao.getResult())
